import Image from 'next/image';
import Callout from '@/components/Callout';
import Details from '@/components/Details';

# `useExtracted`

As an alternative to managing namespaces and keys manually, `next-intl` provides an additional API that works similar to [`useTranslations`](/docs/usage/translations) but automatically extracts messages from your source files.

```tsx
import {useExtracted} from 'next-intl';

function InlineMessages() {
  const t = useExtracted();
  return <h1>{t('Look ma, no keys!')}</h1>;
}
```

Extraction integrates automatically with `next dev` and `next build` via a Turbo- or Webpack loader, you don't need to manually trigger it.

When the above file is compiled, this will:

1. Extract the inline message with an automatically assigned key to your source locale:

```json filename="messages/en.json"
{
  "VgH3tb": "Look ma, no keys!"
}
```

2. Keeps target locales in sync by either adding empty entries or removing outdated ones:

```json filename="messages/de.json"
{
  "VgH3tb": ""
}
```

3. Compiles the file to replace `useExtracted` with `useTranslations`

```tsx
import {useTranslations} from 'next-intl';

function InlineMessages() {
  const t = useTranslations();
  return <h1>{t('VgH3tb')}</h1>;
}
```

**Links:**

- [Blog post](/blog/use-extracted)
- [Example app](/examples#app-router-extracted)

## Getting started

This API is currently experimental, and needs to be enabled in `next.config.ts`:

```tsx filename="next.config.ts"
import {NextConfig} from 'next';
import createNextIntlPlugin from 'next-intl/plugin';

const withNextIntl = createNextIntlPlugin({
  experimental: {
    // Relative path(s) to source files
    srcPath: './src',

    extract: {
      // Defines which locale to extract to
      sourceLocale: 'en'
    },

    messages: {
      // Relative path to the directory
      path: './messages',

      // Either 'json' or 'po'
      format: 'json',

      // Either 'infer' to automatically detect locales based on
      // matching files in `path` or an explicit array of locales
      locales: 'infer'
    }
  }
});

const config: NextConfig = {};
export default withNextIntl(config);
```

With this, every time you call `next dev` or `next build`, messages will be extracted as they are discovered and your messages will be kept in sync.

See [`createNextIntlPlugin`](/docs/usage/plugin#extract) for details.

<Details id="manual">
<summary>Can I extract messages manually?</summary>

While message extraction is designed to seamlessly integrate with your development workflow based on running `next dev` and `next build`, you can also extract messages manually:

```tsx
import {unstable_extractMessages} from 'next-intl/extractor';

await unstable_extractMessages({
  srcPath: './src',
  sourceLocale: 'en',
  messages: {
    path: './messages',
    format: 'json',
    locales: 'infer'
  }
});

console.log('✔ Messages extracted');
```

This can be useful when you're developing a package like a component library, where you don't have a Next.js dev server running and you want to provide messages along with the package.

</Details>

## Inline messages

### ICU messages

All [ICU features](/docs/usage/translations#icu-messages) you are familiar with from `useTranslations` are supported and can be used as usual:

```tsx
// Interpolation of arguments
t('Hello {name}!', {name: 'Jane'});
```

```tsx
// Cardinal pluralization
t(
  'You have {count, plural, =0 {no followers yet} =1 {one follower} other {# followers}}.',
  {count: 3580}
);
```

```tsx
// Ordinal pluralization
t(
  "It's your {year, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} birthday!",
  {year: 22}
);
```

```tsx
// Select values
t('{gender, select, female {She is} male {He is} other {They are}} online.', {
  gender: 'female'
});
```

```tsx
// Rich text
t.rich('Please refer to the <link>guidelines</link>.', {
  link: (chunks) => <Link href="/guidelines">{chunks}</Link>
});
```

The one exception is `t.raw`, this feature is not intended to be used with message extraction.

### Descriptions

In order to provide more context about a message for (AI) translators, you can provide descriptions:

```tsx
<button onClick={onSlideRight}>
  {t({
    message: 'Right',
    description: 'Advance to the next slide'
  })}
</button>
```

### Explicit IDs

If you want to use an explicit ID instead of the auto-generated one, you can optionally provide one:

```tsx
<button onClick={onSlideRight}>
  {t({
    id: 'carousel.next',
    message: 'Right'
  })}
</button>
```

This can be useful when you have a label that is used in multiple places, but should have different translations in other languages. This is an escape hatch that should rarely be necessary.

### Namespaces

If you want to organize your messages under a specific namespace, you can pass it to `useExtracted`:

```tsx
function Modal() {
  const t = useExtracted('design-system');
  return (
    <>
      <button>{t('Close')}</button>
      ...
    </>
  );
}
```

This will extract messages associated with a call to `t` to the given namespace:

```json
{
  "design-system": {
    "5VpL9Z": "Close"
  }
}
```

Namespaces are useful in situations like:

1. **Libraries:** If you have multiple packages in a monorepo, you can merge messages from different packages into a single catalog and avoid key collisions between packages.
2. **Splitting:** If you want to pass only certain messages to the client side, this can help to group them accordingly (e.g. `<NextIntlClientProvider messages={messages.client}>`).

It's a good idea to not overuse namespaces, as they can make moving messages between components more difficult if this involves refactoring the namespace.

### `await getExtracted()` [#get-extracted]

For usage in async functions like Server Components, Metadata and Server Actions, you use an asynchronous variant from `next-intl/server`:

```tsx filename="page.tsx"
import {getExtracted} from 'next-intl/server';

export default async function ProfilePage() {
  const user = await fetchUser();
  const t = await getExtracted();

  return (
    <PageLayout title={t('Hello {name}!', {name: user.name})}>
      <UserDetails user={user} />
    </PageLayout>
  );
}
```

### Optional compilation

While message extraction is primarily designed to be used with a running Next.js app, `useExtracted` works perfectly fine without being compiled into `useTranslations`. In this case, the inline message will be used directly instead of being replaced with a translation key.

This can for example be useful for tests:

```tsx
import {expect, it} from 'vitest';
import {NextIntlClientProvider} from 'next-intl';
import {renderToString} from 'react-dom/server';

function Component() {
  const t = useExtracted();
  return t('Hello {name}!', {name: 'Jane'});
}

it('renders', () => {
  const html = renderToString(
    // No need to pass any messages
    <NextIntlClientProvider locale="en" timeZone="UTC">
      <Component />
    </NextIntlClientProvider>
  );

  // ✅ The inline message will be used
  expect(html).toContain('Hello Jane!');
});
```

## Formatters

Currently, messages can be extracted as either JSON or PO files. Support for custom formatters is planned for a future release.

When [`messages`](/docs/usage/plugin#messages) is configured, this will also set up a Turbo- or Webpack loader that will ensure loaded messages can be imported as plain JavaScript objects.

For example, when using `format: 'po'`, messages can be imported as:

```tsx filename="src/i18n/request.ts"
import {cookies} from 'next/headers';
import {getRequestConfig} from 'next-intl/server';

export default getRequestConfig(async () => {
  const locale = 'en';

  // E.g. `{"NhX4DJ": "Hello"}`
  const messages = (await import(`../../messages/${locale}.po`)).default;

  // ...
});
```

### JSON formatter [#formatters-json]

When using this option, your messages will look like this:

```json
{
  "NhX4DJ": "Hello"
}
```

Note that JSON files can only hold pairs of keys and values. To provide more context about a message like file references and descriptions, it's therefore recommended to use [PO files](#po) instead. Another alternative will be to use a custom JSON formatter in the future.

For local editing of JSON messages, you can use e.g. a [VSCode integration](/docs/workflows/vscode-integration) like i18n Ally:

<Image
  className="my-4"
  src="/i18n-ally-extracted-message.png"
  alt="i18n Ally extracted message"
  width={400}
  height={187}
/>

<Callout>

**Tip:** AI-based translation can be automated with a translation management system like [Crowdin](/docs/workflows/localization-management).

</Callout>

### PO formatter [#formatters-po]

When using this option, your messages will look like this:

```po
#. Advance to the next slide
#: src/components/Carousel.tsx:13
msgid "5VpL9Z"
msgstr "Right"
```

Besides the message key and the label itself, this format also supports optional descriptions and file references to all modules that consume this message.

For local editing of PO messages, you can use e.g. a tool like [Poedit](https://poedit.net/) (replacing keys with source text requires a pro license).

<Callout>

**Tip:** AI-based translation can be automated with a translation management system like [Crowdin](/docs/workflows/localization-management).

</Callout>
